# preshrunk-cotton
# Copyright (C) 2010, 2011 Approximatrix, LLC
# http://code.google.com/p/preshrunk-cotton/
# http://approximatrix.com/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# A full version of the license terms is available in LICENSE.

import os
import os.path
from xml.dom.minidom import getDOMImplementation
import re
from subprocess import Popen

from pscotton.index import Indexer
from pscotton.simpletoc import SimpleTOC, DefaultTOC

TOC_FILE = '_table_of_contents.hhc'
INDEX_FILE = '_index.hhk'

TOC_HEADER = """
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
<!-- Generated via preshrunk-cotton -->
<!-- Sitemap 1.0 -->
</HEAD><BODY>
<OBJECT type="text/site properties">
</OBJECT>
"""

TOC_FOOTER = "</BODY></HTML>"

DIR_ENTRY = """<LI> <OBJECT type="text/sitemap">
        <param name="Name" value="{name}">
        <param name="ImageNumber" value="0">
    </OBJECT>
"""

FILE_ENTRY = """<LI> <OBJECT type="text/sitemap">
        <param name="Name" value="{name}">
        <param name="Local" value="{file}">
        <param name="ImageNumber" value="0">
    </OBJECT>
"""

TITLE_FILES = "title.txt"

IGNORE_EXTENSIONS = ('.hhc', '.hhk', '.hfp', '.chm')

class CHMProject:
    """Class for controlling the compression of a specified directory
    structure into a CHM file.  To construct a CHM, the following
    sequence should generally be followed:

    1. generate_toc() (optional)
    2. generate_index() (optional)
    3. generate_hfp() (required)
    4. chmcmd() (required)

    This class requires that 'chmcmd' program is available on the
    current path.  'chmcmd' is part of Free Pascal and the associated
    Lazarus IDE (http://www.freepascal.org/).
    """

    def __init__(self,directory,chmfile):
        """Creates a CHMProject object, which can create a Compressed HTML Help
        (CHM) file from a directory tree.  The directory and the desired name of
        the resultant CHM file must be specified when calling the contructor.
        """

        self.directory = directory
        self.files = []
        self.chmfile = chmfile
        self.projectfile = None
        self.indexbuilt = False
        self.tocbuilt = False

    def generate_index(self,ignore=[]):
        """Generates an index file by reading all html files below the
        specified root directory and generating an index entry for all
        headings in the html file.  An ignore list can be specified,
        which should be a list containing possible heading text that
        should not be included in the index (useful if every html file
        has a common heading).
        """
        
        indexer = Indexer(self.directory)
        indexer.process()
        indexer.output(os.path.join(self.directory, INDEX_FILE))

        self.indexbuilt = True

    def generate_toc(self,tocspec=None):
        """Generates a table of contents (TOC) file for the CHM by walking the
        specified directory, creating a tree based on directory structure. Headings
        for each directory are generated by looking for 'title.txt' files in each
        directory.  These files should contain a single line with the desired
        section heading for the table of contents (the tree displayed in the
        help browser).  Each page's title in the table of contents is read in from
        each html file's title.
        """

        tree = None
        if tocspec:
            tree = SimpleTOC(tocspec)
            tree.root.name = self.directory
        else:
            tree = DefaultTOC(self.directory)

        tree.process()

        toc = open(os.path.join(self.directory, TOC_FILE), "w")
        toc.write(TOC_HEADER)
        toc.write("<UL>\n")
        self.tocwalker(tree.root, toc)
        toc.write("</UL>\n")
        toc.write(TOC_FOOTER)
        toc.close()

        self.tocbuilt = True

    def get_html_title(self,filename):
        try:
            f = open(filename,"r")
            contents = f.read()
            pat = re.compile("<title>(.*?)<\/title>",
                             re.DOTALL|re.M|re.IGNORECASE)
            title = pat.findall(contents)[0]
            f.close()
            return title
        except:
            return filename

    def tocwalker(self,node,toc):
        title = node.name
        try:
            tf = open(os.path.join(node.path(), TITLE_FILES),"r")
            title = tf.readline().strip()
            tf.close()
        except:
            pass

        toc.write(DIR_ENTRY.format(name=title))

        reldir = os.path.relpath(node.path(), self.directory)
        if reldir == '.':
            reldir = ''

        toc.write("<UL>\n")

        for x in node.children:
            if x.isdir():
                self.tocwalker(x,toc)
            else:
                title = self.get_html_title(x.path())
                if title == None:
                    title = x.name
                    
                filename = os.path.join(reldir,x.name)
                    
                if os.sep != '/':
                    filename = filename.replace(os.sep,'/')
                    
                toc.write(FILE_ENTRY.format(name=title,
                                            file=filename))

        toc.write("</UL>\n")

    def load_files(self):
        
        for (dirname, directories, files) in os.walk(self.directory):
        
            reldir = os.path.relpath(dirname, self.directory)
            if reldir == '.':
                reldir = ''

            if os.path.basename(dirname).startswith('.'):
                return

            for f in files:
                base, ext = os.path.splitext(f)
                if ext in IGNORE_EXTENSIONS:
                    continue
                if f.startswith('.'):
                    continue
                if os.path.isfile(os.path.join(self.directory, reldir, f)):
                    filename = os.path.join(reldir, f)
                    if os.sep != '/':
                        filename = filename.replace(os.sep, '/')
                    self.files.append(filename)

    def chmcmd(self):
        """Executes the 'chmcmd' process, constructing a CHM file.  A proper
        CHM file will only be built if the other necessary methods have been
        executed."""
        if self.projectfile == None:
            raise ValueError('A project file has not yet been assigned/built')

        output = Popen(['chmcmd',self.projectfile],cwd=self.directory)
        output.wait()
        #os.system('chmcmd '+self.projectfile)

    def generate_hfp(self,startPage=None,title=None):
        """Generates an HTML Help project file compatible with Free Pascal's
        'chmcmd' executable.  The routine's output will be an XML file.  The
        project file contains all files in the directory tree, which are
        assumed to be necessary to complete the CHM file.  If the methods
        generate_index() and generate_toc() have already been called, the
        resultant files are tagged and included properly in the project file."""

        self.load_files()

        [base,chmext] = os.path.splitext(self.chmfile)
        self.projectfile = base+'.hfp'
        outfile = os.path.join(self.directory,self.projectfile)

        impl = getDOMImplementation()

        # Create the minidom document
        #doc = Document()
        doc = impl.createDocument(None, "CONFIG", None)

        # Create the <CONFIG> base element
        cfg = doc.documentElement

        # Files section
        filegroup = doc.createElement("Files")
        cfg.appendChild(filegroup)

        count = doc.createElement("Count")
        count.setAttribute("Value", str(len(self.files)))
        filegroup.appendChild(count)

        fcount = 0

        for file in self.files:
            f = doc.createElement("FileName"+str(fcount))
            f.setAttribute("Value", file)
            filegroup.appendChild(f)
            if startPage== None:
                [base,ext] = os.path.splitext(file)
                if ext.endswith('html'):
                    startPage = file
            fcount = fcount + 1

        if self.tocbuilt:
            tcf = doc.createElement("IndexFile")
            tcf.setAttribute("Value", INDEX_FILE)
            filegroup.appendChild(tcf)

        if self.tocbuilt:
            tcf = doc.createElement("TOCFile")
            tcf.setAttribute("Value", TOC_FILE)
            filegroup.appendChild(tcf)


        settings = doc.createElement("Settings")
        cfg.appendChild(settings)

        searchable = doc.createElement("MakeSearchable")
        searchable.setAttribute("Value", "False")
        settings.appendChild(searchable)

        if startPage== None:
            startPage = 'index.html'
        dflt = doc.createElement("DefaultPage")
        dflt.setAttribute("Value", startPage)
        settings.appendChild(dflt)

        if title == None:
            title = ""

        te = doc.createElement("Title")
        te.setAttribute("Value",title)
        settings.appendChild(te)

        ofn = doc.createElement("OutputFileName")
        ofn.setAttribute("Value", self.chmfile)
        settings.appendChild(ofn)

        fnt = doc.createElement("DefaultFont")
        fnt.setAttribute("Value", "")
        settings.appendChild(fnt)

        f = open(outfile, 'w')
        f.write(doc.toprettyxml(indent="  "))
        f.close()

